Segment 3: Provide Context with Annotations

Part 3 of the Pearson Live Training Session “Hands–On Data Visualization with ggplot2” for O’Reilly

Cédric Scherer https://cedricscherer.com
library(tidyverse)
theme_set(theme_light(base_size = 18))
data <- readr::read_csv("https://raw.githubusercontent.com/z3tt/hands-on-ggplot2/main/data/crypto_cleaned.csv")

Adjust Colors and Other aesthetics

One can use scale_*() to change properties of all the aesthetic dimensions mapped to the data.

Consequently, there are scale_*() functions for all aesthetics such as:

The extensions (*) can be filled by e.g.:

Scales: scale_*()

Scales are directly connected to aesthetics:

ggplot(data, aes(x = date, y = close, color = currency)) +
  geom_line() +
  scale_x_date() + 
  scale_y_continuous() + 
  scale_color_discrete()

All scales come with some general and specific arguments to change the appearance:

ggplot(data, aes(x = date, y = close, color = currency)) +
  geom_line() +
  scale_x_date(
    expand = c(0, 0), ## general
    date_breaks = "4 months", ## date-only 
    date_labels = "%m/%y", ## date only
    name = NULL ## general 
  ) +
  scale_y_continuous() +  
  scale_color_discrete() 

ggplot(data, aes(x = date, y = close, color = currency)) +
  geom_line() +
  scale_x_date(
    expand = c(0, 0), ## general
    date_breaks = "4 months", ## date-only  
    date_labels = "%m/%y", ## date only
    name = NULL ## general 
  ) +
  scale_y_continuous(
    labels = scales::dollar_format(), ## general 
    sec.axis = dup_axis(name = NULL), ## axis only
    name = "Closing Price" ## general 
  ) + 
  scale_color_discrete()

ggplot(data, aes(x = date, y = close, color = currency)) +
  geom_line() +
  scale_x_date(
    expand = c(0, 0), ## general 
    date_breaks = "4 months", ## date-only 
    date_labels = "%m/%y", ## date only 
    name = NULL ## general 
  ) +
  scale_y_continuous(
    labels = scales::dollar_format(), ## general  
    sec.axis = dup_axis(name = NULL), ## axis only 
    name = "Closing Price" ## general 
  ) + 
  scale_color_discrete(
    type = c("#F0B90B", "#4d4d4e", "#810080", "#00aeff"),  ## color only
    name = "Cryptocurrency:" ## general 
  )

Illustration by Allison Horst
Color palette types

Scales: scale_color_*()

All colors and fills that are mapped to categorical variables can be manipulated with either scale_color|fill_discrete() or scale_color|fill_manual().

ggplot(data, aes(x = date, y = close, color = currency)) +
  geom_line() +
  scale_color_discrete(type = c("#F0B90B", "#4d4d4e","#810080", "#00aeff"))

ggplot(data, aes(x = date, y = close, color = currency)) +
  geom_line() +
  scale_color_manual(values = c("#F0B90B", "#4d4d4e", "#810080", "#00aeff"))

Here, you can overwrite the labels of your legend items—no need to manipulate the data itself!

ggplot(data, aes(x = date, y = close, color = currency)) +
  geom_line() +
  scale_color_manual(
    values = c("#F0B90B", "#4d4d4e", "#810080", "#00aeff"),
    labels = c("Binance", "Bitcoin", "EOS", "Litecoin"),
    name = "Cryptocurrency:" 
  )

Comparison of continuous color palettes, taken from the {viridis} package reference
The same collection of palettes for diferent color vision deficiencies

There are a few and well crafted built-in palettes you can use as well (the defaults are pretty bad).

Scales: scale_color_viridis_*()

ggplot(data, aes(x = date, y = close, color = currency)) +
  geom_line() +
  scale_color_viridis_d()

ggplot(data, aes(x = date, y = close, color = currency)) +
  geom_line() +
  scale_color_viridis_d(
    option = "turbo",
    begin = .1,
    end = .9,
    name = "Cryptocurrency:"
  )

Color palettes contained in the latest version of the {viridis} package, taken from the {viridis} package reference

The viridis palettes are more suitable for continuous data:

ggplot(data, aes(x = date, y = close, color = yday, group = currency)) +
  geom_line() +
  scale_color_viridis_c(
    option = "turbo",
    begin = .1,
    end = .9, 
    name = "Julian Day:"
  )

ggplot(data, aes(x = date, y = close, color = open, group = currency)) +
  geom_point() +
  scale_color_viridis_c(
    option = "rocket",
    direction = -1,
    end = .9,
    labels = scales::dollar_format(),
    name = "Open Price:"
  )

ggplot(data, aes(x = date, y = close, color = open, group = currency)) +
  geom_point() +
  scale_color_viridis_c(
    option = "rocket",
    direction = -1, 
    end = .9,
    breaks = seq(50, 300, by = 100),
    labels = scales::dollar_format(), 
    name = "Open Price:"
  )

Scales: scale_color_brewer_*()

Colorbrewer provides color schemes for maps.

RColorBrewer::display.brewer.all()

ggplot(data, aes(x = date, y = close, color = currency)) +
    geom_line() +
    scale_color_brewer(
        palette = "Set2",
        name = "Cryptocurrency:"
    )


Exercise 1

Colorbrewer provides color schemes for maps.


Scales: scale_fill_gradient()`

You can also build your own sequential color palettes:

sf_world <- rnaturalearth::ne_countries(returnclass = "sf")

ggplot(sf_world) +
  geom_sf(aes(fill = pop_est), color = NA) + 
  scale_fill_gradient(low = "grey90", high = "firebrick4") +
  coord_sf(crs = "+proj=moll")

Scales: scale_fill_gradient2()`

You can also build your own diverging color palettes:

ggplot(sf_world) +
  geom_sf(aes(fill = as.numeric(factor(income_grp))), color = NA) +
  scale_fill_gradient2(low = "firebrick4", high = "darkcyan", mid = "grey90", name = "Income group:") +
  coord_sf(crs = "+proj=moll")

ggplot(sf_world) +
  geom_sf(aes(fill = as.numeric(factor(income_grp))), color = NA) +
  scale_fill_gradient2(low = "firebrick4", high = "darkcyan", mid = "grey90", name = "Income group:", 
                       midpoint = 3) +
  coord_sf(crs = "+proj=moll")

Color Palettes in R

Several packages offer predefined palettes, e.g.:

Check the collection by Emil Hvitfeldt for an extensive list of color palettes available in R

A ggplot Object

By the way, you can store ggplots in objects and extend them later:

g <- ggplot(sf_world) +
  geom_sf(aes(fill = as.numeric(factor(income_grp))), color = NA) +
  scale_fill_gradient2(low = "firebrick4", high = "darkcyan", mid = "grey90", name = "Income group:", 
                       midpoint = 3) +
  coord_sf(crs = "+proj=moll")

class(g)
[1] "gg"     "ggplot"

Legends (Guides)

Style Legends: guides()

The guides() function allows to adjust the appearance of each legend:

g +
  guides(fill = guide_colorbar())

g +
  guides(fill = guide_legend())

g +
  guides(fill = guide_legend(reverse = TRUE))

g +
  guides(fill = guide_legend(reverse = TRUE, keyheight = unit(2, "lines")))

g +
  guides(fill = guide_colorbar(barheight = unit(12, "lines"),
                               barwidth = unit(1, "lines")))


Exercise 2


Style Legends: theme()

The position of the legend and some other properties can be changed via theme():

g +
  guides(fill = guide_legend()) +
  theme(legend.position = "top")

g +
  guides(fill = guide_legend()) +
  theme(legend.position = "top", legend.key.height = unit(.5, "lines"),
        legend.key.width = unit(4, "lines"))

You can change the position of the legend title and labels in guides():

g +
  guides(fill = guide_legend(title.position = "top", title.hjust = .5, label.position = "bottom")) +
  theme(legend.position = "top", legend.key.height = unit(.5, "lines"),
        legend.key.width = unit(4, "lines")) 

Template and Custom Theming

Themes: theme_*()

You have already seen built-in themes in segment 1:

gg <- ggplot(data, aes(x = date, y = close, color = currency)) +
       geom_line(size = 1.5)

gg +
  theme_grey() +
  ggtitle("theme_grey()  or  theme_gray()") 
gg +
  theme_classic() +
  ggtitle("theme_classic()") 
gg +
  theme_bw() +
  ggtitle("theme_bw()") 
gg +
  theme_light() +
  ggtitle("theme_light()")
gg +
  theme_minimal() +
  ggtitle("theme_minimal()") 
gg +
  theme_dark() +
  ggtitle("theme_dark()") 
gg +
  theme_linedraw() +
  ggtitle("theme_linedraw()")
gg +
  theme_void() +
  ggtitle("theme_void()") 

You can either add theme changes to each plot or globally for all plots:

ggplot(data, aes(x = date, y = close)) +
  geom_line(aes(color = currency)) + 
  theme_classic(base_size = 16)

theme_set(theme_classic(base_size = 16))
ggplot(data, aes(x = date, y = close)) +
  geom_line(aes(color = currency))

theme_grey
function (base_size = 11, base_family = "", base_line_size = base_size/22, 
    base_rect_size = base_size/22) 
{
    half_line <- base_size/2
    t <- theme(line = element_line(colour = "black", size = base_line_size, 
        linetype = 1, lineend = "butt"), rect = element_rect(fill = "white", 
        colour = "black", size = base_rect_size, linetype = 1), 
        text = element_text(family = base_family, face = "plain", 
            colour = "black", size = base_size, lineheight = 0.9, 
            hjust = 0.5, vjust = 0.5, angle = 0, margin = margin(), 
            debug = FALSE), axis.line = element_blank(), axis.line.x = NULL, 
        axis.line.y = NULL, axis.text = element_text(size = rel(0.8), 
            colour = "grey30"), axis.text.x = element_text(margin = margin(t = 0.8 * 
            half_line/2), vjust = 1), axis.text.x.top = element_text(margin = margin(b = 0.8 * 
            half_line/2), vjust = 0), axis.text.y = element_text(margin = margin(r = 0.8 * 
            half_line/2), hjust = 1), axis.text.y.right = element_text(margin = margin(l = 0.8 * 
            half_line/2), hjust = 0), axis.ticks = element_line(colour = "grey20"), 
        axis.ticks.length = unit(half_line/2, "pt"), axis.ticks.length.x = NULL, 
        axis.ticks.length.x.top = NULL, axis.ticks.length.x.bottom = NULL, 
        axis.ticks.length.y = NULL, axis.ticks.length.y.left = NULL, 
        axis.ticks.length.y.right = NULL, axis.title.x = element_text(margin = margin(t = half_line/2), 
            vjust = 1), axis.title.x.top = element_text(margin = margin(b = half_line/2), 
            vjust = 0), axis.title.y = element_text(angle = 90, 
            margin = margin(r = half_line/2), vjust = 1), axis.title.y.right = element_text(angle = -90, 
            margin = margin(l = half_line/2), vjust = 0), legend.background = element_rect(colour = NA), 
        legend.spacing = unit(2 * half_line, "pt"), legend.spacing.x = NULL, 
        legend.spacing.y = NULL, legend.margin = margin(half_line, 
            half_line, half_line, half_line), legend.key = element_rect(fill = "grey95", 
            colour = NA), legend.key.size = unit(1.2, "lines"), 
        legend.key.height = NULL, legend.key.width = NULL, legend.text = element_text(size = rel(0.8)), 
        legend.text.align = NULL, legend.title = element_text(hjust = 0), 
        legend.title.align = NULL, legend.position = "right", 
        legend.direction = NULL, legend.justification = "center", 
        legend.box = NULL, legend.box.margin = margin(0, 0, 0, 
            0, "cm"), legend.box.background = element_blank(), 
        legend.box.spacing = unit(2 * half_line, "pt"), panel.background = element_rect(fill = "grey92", 
            colour = NA), panel.border = element_blank(), panel.grid = element_line(colour = "white"), 
        panel.grid.minor = element_line(size = rel(0.5)), panel.spacing = unit(half_line, 
            "pt"), panel.spacing.x = NULL, panel.spacing.y = NULL, 
        panel.ontop = FALSE, strip.background = element_rect(fill = "grey85", 
            colour = NA), strip.text = element_text(colour = "grey10", 
            size = rel(0.8), margin = margin(0.8 * half_line, 
                0.8 * half_line, 0.8 * half_line, 0.8 * half_line)), 
        strip.text.x = NULL, strip.text.y = element_text(angle = -90), 
        strip.text.y.left = element_text(angle = 90), strip.placement = "inside", 
        strip.placement.x = NULL, strip.placement.y = NULL, strip.switch.pad.grid = unit(half_line/2, 
            "pt"), strip.switch.pad.wrap = unit(half_line/2, 
            "pt"), plot.background = element_rect(colour = "white"), 
        plot.title = element_text(size = rel(1.2), hjust = 0, 
            vjust = 1, margin = margin(b = half_line)), plot.title.position = "panel", 
        plot.subtitle = element_text(hjust = 0, vjust = 1, margin = margin(b = half_line)), 
        plot.caption = element_text(size = rel(0.8), hjust = 1, 
            vjust = 1, margin = margin(t = half_line)), plot.caption.position = "panel", 
        plot.tag = element_text(size = rel(1.2), hjust = 0.5, 
            vjust = 0.5), plot.tag.position = "topleft", plot.margin = margin(half_line, 
            half_line, half_line, half_line), complete = TRUE)
    ggplot_global$theme_all_null %+replace% t
}
<bytecode: 0x000000002148b988>
<environment: namespace:ggplot2>

theme() Arguments

There are many elements you can customize. You can either group them by their type or by their category:

You can directly alter the appearance by adding theme() to a ggplot.

Rectangular Elements via element_rect()

Theme components that are element_rect (Part 1)
Theme components that are element_rect (Part 2)
my_rect_element <- element_rect(
    color = "black",
    fill = "orange",
    size = 2,
    linetype = "dotted"
  )

g +
  theme(plot.background = my_rect_element, legend.background = my_rect_element)

Line Elements via element_line()

Theme components that are element_line
g +
  theme(panel.grid = element_line(
    color = "black",
    size = 3,
    linetype = "dashed",
    lineend = "square", # round, butt
    arrow = arrow(angle = 30, length = unit(0.25, "inches"))
  ))

Text Elements via element_text()

Theme components that are element_text
my_text_element <- element_text(
    #family = "Roboto", ## you need {systemfonts} for this
    face = "bold", ## plain, italic, bolditalic
    size = 24,
    color = "firebrick",
    lineheight = .7,
    angle = 180,
    hjust = .5,
    vjust = .0,
    margin = margin(
      10, ## t (top)
      0,  ## r (right)
      30, ## b (bottom)
      0   ## l (left)
    )
  )

g +
  ggtitle("My New\nTitle") +
  theme(plot.title = my_text_element, legend.text = my_text_element)


Exercise 3


Resources


Session Info

Expand for details
[1] "2022-06-25 13:59:10 CEST"
Local:    main C:/Users/DataVizard/Google Drive/Work/DataViz/Teaching/2022_OReilly_Trainings/hands-on-ggplot2-training
Remote:   main @ origin (https://github.com/z3tt/hands-on-ggplot2-training.git)
Head:     [1cc6183] 2022-02-18: fix typo
R version 4.1.0 (2021-05-18)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows 10 x64 (build 19043)

Matrix products: default

locale:
[1] LC_COLLATE=German_Germany.1252  LC_CTYPE=German_Germany.1252   
[3] LC_MONETARY=German_Germany.1252 LC_NUMERIC=C                   
[5] LC_TIME=German_Germany.1252    
system code page: 65001

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods  
[7] base     

other attached packages:
[1] forcats_0.5.1   stringr_1.4.0   dplyr_1.0.7     purrr_0.3.4    
[5] readr_2.0.2     tidyr_1.1.4     tibble_3.1.6    ggplot2_3.3.5  
[9] tidyverse_1.3.1

loaded via a namespace (and not attached):
 [1] fs_1.5.0            sf_1.0-5            lubridate_1.8.0    
 [4] bit64_4.0.5         RColorBrewer_1.1-2  httr_1.4.2         
 [7] tools_4.1.0         backports_1.2.1     bslib_0.3.1        
[10] utf8_1.2.2          R6_2.5.1            KernSmooth_2.23-20 
[13] DBI_1.1.2           colorspace_2.0-2    withr_2.4.3        
[16] sp_1.4-6            tidyselect_1.1.1    downlit_0.4.0      
[19] git2r_0.29.0        bit_4.0.4           curl_4.3.2         
[22] compiler_4.1.0      textshaping_0.3.6   cli_3.1.0          
[25] rvest_1.0.2         xml2_1.3.2          labeling_0.4.2     
[28] sass_0.4.0          scales_1.1.1        classInt_0.4-3     
[31] proxy_0.4-26        systemfonts_1.0.3   digest_0.6.29      
[34] rmarkdown_2.11      pkgconfig_2.0.3     htmltools_0.5.2    
[37] dbplyr_2.1.1        fastmap_1.1.0       highr_0.9          
[40] rlang_1.0.2         readxl_1.3.1        rstudioapi_0.13    
[43] jquerylib_0.1.4     farver_2.1.0        generics_0.1.1     
[46] jsonlite_1.7.2      vroom_1.5.5         distill_1.3        
[49] magrittr_2.0.1      Rcpp_1.0.7          munsell_0.5.0      
[52] fansi_0.5.0         lifecycle_1.0.1     stringi_1.7.5      
[55] yaml_2.2.1          grid_4.1.0          parallel_4.1.0     
[58] crayon_1.4.2        lattice_0.20-44     haven_2.4.3        
[61] hms_1.1.1           knitr_1.39          pillar_1.6.4       
[64] reprex_2.0.1        glue_1.4.2          evaluate_0.15      
[67] modelr_0.1.8        vctrs_0.3.8         tzdb_0.1.2         
[70] cellranger_1.1.0    gtable_0.3.0        assertthat_0.2.1   
[73] cachem_1.0.6        xfun_0.31           broom_0.8.0        
[76] e1071_1.7-9         rnaturalearth_0.1.0 ragg_1.1.3         
[79] class_7.3-19        viridisLite_0.4.0   memoise_2.0.1      
[82] units_0.7-2         ellipsis_0.3.2